Skip to content

feat: add standalone Klarna widget component#455

Open
ArushKapoorJuspay wants to merge 2 commits into
mainfrom
feat/klarna-widget-a
Open

feat: add standalone Klarna widget component#455
ArushKapoorJuspay wants to merge 2 commits into
mainfrom
feat/klarna-widget-a

Conversation

@ArushKapoorJuspay

@ArushKapoorJuspay ArushKapoorJuspay commented Mar 29, 2026

Copy link
Copy Markdown
Contributor

Summary

Add a self-contained Klarna widget with a dedicated KlarnaWidget sdkState that wraps the existing <Klarna> component. The widget renders the Klarna SDK inline, handles payment authorization, and confirms via the payments API.

Changes

New: src/pages/widgets/KlarnaWidgetWrapper.res (+145 lines)

  • Standalone widget wrapper that reuses the existing <Klarna> component
  • Implements the widget communication protocol: sends readyMessage on mount, receives credentials via native "widget" event
  • Uses nativePropRef pattern to prevent stale closures
  • Extracts Klarna session token from sessions API response using wallet_name_str field (not wallet_name enum which mapped "klarna" to NONE)
  • Supports auto-confirm via setLaunchKlarna(_ => Some("launch")) when confirm: true
  • Handles Klarna SDK authorization callback → builds pay_later.klarna_sdk.token confirm body → fetchAndRedirect
  • Real PML lookup from accountPaymentMethodData context — finds payment_method_type == "klarna"
  • isKlarnaAvailable memo gated on INVOKE_SDK_CLIENT payment experience (only Klarna with SDK integration is supported)
  • Required-fields pipeline: calls getRequiredFieldsForButton from DynamicFieldsContext before confirm
  • Uses PaymentUtils.generateCardConfirmBody + CommonUtils.mergeDict for confirm body (canonical pattern)
  • Render gated: only renders when klarnaSessionToken !== "" AND isKlarnaAvailable

src/types/SdkTypes.res (+3 lines)

  • Added KlarnaWidget to sdkState variant type
  • Added KlarnaWidget to sdkStateToStrMapper (returns "klarna")
  • Added "klarna" case to sdkState parser

src/types/AllApiDataTypes/SessionsType.res (+3/-1 lines)

  • Added wallet_name_str: string field to sessions record — stores raw wallet name string for reliable matching (the existing wallet_name enum mapped "klarna" to NONE)
  • Updated defaultToken and itemToObjMapper to include the new field

src/routes/NavigationRouter.res (+1 line)

  • Added KlarnaWidget => <KlarnaWidgetWrapper /> route

src/hooks/AllPaymentHooks.res (+1 line)

  • Added KlarnaWidget => exitWidget(apiResStatus, "klarna") to success/failure handler

src/utility/reusableCodeFromWeb/ErrorHooks.res (+2/-2 lines)

  • Added KlarnaWidget to 2 exhaustive switch statements

src/components/elements/LoadingOverlay.res (+1/-1 line)

  • Added KlarnaWidget to loading overlay switch

Architecture Decision

This PR uses Approach A (dedicated KlarnaWidget sdkState) rather than Approach B (adding KLARNA to the wallet enum). Approach A scored 6/10 vs Approach B's 4/10 because:

  • Klarna is a pay_later type, NOT a wallet — adding it to the wallet enum would be semantically wrong
  • Session token matching requires wallet_name_str (raw string) since the enum maps "klarna" to NONE
  • The INVOKE_SDK_CLIENT payment experience check is specific to Klarna — other wallet widgets use different experience types

Data Flow

  • Session token: From AllApiDataContextNewsessionTokenData → find wallet_name_str == "klarna"
  • PML data: From AllApiDataContextNewaccountPaymentMethodData → find payment_method_type == "klarna"
  • Availability gate: isKlarnaAvailable checks PML entry has INVOKE_SDK_CLIENT in payment_experience
  • Required fields: From DynamicFieldsContextgetRequiredFieldsForButton(paymentMethodData)
  • Confirm body: PaymentUtils.generateCardConfirmBody merged with pay_later.klarna_sdk.token via CommonUtils.mergeDict

Widget Communication Flow

1. Widget mounts → NativeEventListener.sendReadyMessage("klarna")
2. Native sends "widget" event → {clientSecret, publishableKey, confirm, paymentMethodType}
3. Widget updates nativeProp context with credentials
4. Sessions + PML data load → klarnaSessionToken extracted → isKlarnaAvailable computed
5. If confirm=true and data ready, auto-launches Klarna SDK
6. Klarna SDK renders inline → user completes → authorization callback
7. getRequiredFieldsForButton check → generateCardConfirmBody + mergeDict
8. POST /payments/{id}/confirm → exitWidget(status, "klarna")

Testing

  • ReScript compilation passes (npm run re:check with -warn-error +a-4-9)
  • Security scan passes (gitleaks)
  • Requires native integration testing with Klarna sandbox for end-to-end verification

- Add KlarnaWidget sdkState variant with dedicated KlarnaWidgetWrapper.res
- Use wallet_name_str field in SessionsType for reliable Klarna session matching
- Use nativePropRef pattern to avoid stale closure in processRequest
- Wire up NavigationRouter, AllPaymentHooks, ErrorHooks, LoadingOverlay
- Correct confirm body: pay_later.klarna_sdk.token (not wallet.*)
@ArushKapoorJuspay ArushKapoorJuspay added the ai-generated PR generated by AI. Requires human review for correctness and security. label Mar 31, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ai-generated PR generated by AI. Requires human review for correctness and security.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant